#include "./gap.h"
#include "../main.h"
#include "./gattclient.h"
#include "./links.h"
#include "ble/BLE.h"
#include "pretty_printer.h"

GapDemo gap;

GapDemo::GapDemo() : _adv_data_builder(_adv_buffer) {}
GapDemo::~GapDemo() {}

void GapDemo::init(ble::Gap &gap) {
  _gap = &gap;
  _gap->setEventHandler(this);

  if (_gap->isFeatureSupported(
          ble::controller_supported_features_t::LE_CODED_PHY)) {
    ble::phy_set_t phys(/* 1M */ false, /* 2M */ false, /* coded */ true);
    ble_error_t error = _gap->setPreferredPhys(/* tx */ &phys, /* rx */ &phys);
    if (error) {
      print_error(error, "GAP::setPreferedPhys failed");
    }
  } else {
    printf("don't support coded.\r\n");
  }
  init_adv();
  init_scan();
}
void GapDemo::init_adv(void) {
  ble::AdvertisingParameters adv_parameters(
      ble::advertising_type_t::CONNECTABLE_UNDIRECTED,
      ble::adv_interval_t(ble::millisecond_t(2000)),
      ble::adv_interval_t(ble::millisecond_t(3000)));
  ble_error_t error = _gap->setAdvertisingParameters(
      ble::LEGACY_ADVERTISING_HANDLE, adv_parameters);
  if (error) {
    print_error(error, "Gap::createAdvertisingSet() failed\r\n");
    return;
  }

  uint8_t mdata[3] = {0x64, 0x64, 01};
  _adv_data_builder.setManufacturerSpecificData(make_Span(mdata, 3));
  _adv_data_builder.setFlags();
  _adv_data_builder.setName("unmp_router");
  error = _gap->setAdvertisingPayload(ble::LEGACY_ADVERTISING_HANDLE,
                                      _adv_data_builder.getAdvertisingData());
  if (error) {
    print_error(error, "Gap::setAdvertisingPayload() failed\r\n");
    return;
  }
}
void GapDemo::init_scan(void) {
  ble::ScanParameters scan_params;
  scan_params.setOwnAddressType(ble::own_address_type_t::RANDOM);
  ble_error_t error = _gap->setScanParameters(scan_params);
  if (error) {
    print_error(error, "Error caused by Gap::setScanParameters\r\n");
    return;
  }
}

void GapDemo::start_advertise() {
  ble_error_t error = _gap->startAdvertising(ble::LEGACY_ADVERTISING_HANDLE);
  if (error) {
    print_error(error, "Gap::startAdvertising() failed\r\n");
    return;
  }
  printf("Advertising started\r\n");
}

void GapDemo::start_scan() {
  ble_error_t error =
      _gap->startScan(ble::scan_duration_t(ble::millisecond_t(5000)));
  if (error) {
    print_error(error, "Error caused by Gap::startScan\r\n");
    return;
  }
  printf("Scanning started\r\n");
}

void GapDemo::update_connParas(ble::connection_handle_t connection_handle) {
  _gap->updateConnectionParameters(
      connection_handle, ble::conn_interval_t(ble::millisecond_t(250)),
      ble::conn_interval_t(ble::millisecond_t(300)), ble::slave_latency_t(1),
      ble::supervision_timeout_t(ble::millisecond_t(5000)));
}

void GapDemo::disconnect(ble::connection_handle_t connection_handle,
                         ble::local_disconnection_reason_t reason) {
  ble_error_t error = _gap->disconnect(connection_handle, reason);
  if (error) {
    print_error(error, "Error caused by Gap::disconnect.\r\n");
    return;
  }
}

/*--------------- Gap::EventHandler -------------------*/
void GapDemo::onAdvertisingEnd(const ble::AdvertisingEndEvent &event) {
  printf("AdvertisingEnd\r\n");
  event_queue.call(this, &GapDemo::start_advertise);
}

/** Look at scan payload to find a peer device and connect to it */
void GapDemo::onAdvertisingReport(const ble::AdvertisingReportEvent &event) {
  ble::AdvertisingDataParser adv_parser(event.getPayload());
  while (adv_parser.hasNext()) {
    ble::AdvertisingDataParser::element_t field = adv_parser.next();
    if (field.type != ble::adv_data_type_t::MANUFACTURER_SPECIFIC_DATA) {
      continue;
    }
    auto value = field.value.data();
    if (value[0] != 0x64 || value[1] != 0x64) {
      continue;
    }
    BleLinks::Link *link =
        blelinks.get_link_by_mac(event.getPeerAddress().data());
    if (link == nullptr) {
      link = blelinks.add_link(-1, event.getPeerAddress().data());
    }
    if (link->handle != -1) {
      return;
    }
    print_address(event.getPeerAddress());

    ble_error_t error =
        _gap->connect(event.getPeerAddressType(), event.getPeerAddress(),
                      ble::ConnectionParameters());
    if (error) {
      print_error(error, "Error caused by Gap::connect\r\n");
      return;
    }
  }
}

void GapDemo::onScanTimeout(const ble::ScanTimeoutEvent &event) {
  printf("ScanTimeout\r\n");
}

/** This is called by Gap to notify the application we connected */
void GapDemo::onConnectionComplete(const ble::ConnectionCompleteEvent &event) {
  if (event.getStatus() == BLE_ERROR_NONE) {
    ble::connection_handle_t connection_handle = event.getConnectionHandle();
    printf("Connected to: ");
    print_address(event.getPeerAddress());

    BleLinks::Link *link =
        blelinks.get_link_by_mac(event.getPeerAddress().data());
    if (link == nullptr) {
      link =
          blelinks.add_link(connection_handle, event.getPeerAddress().data());
    }
    link->handle = connection_handle;

    gattclient.start_discovery(connection_handle);
    event_queue.call_in(
        2s, [connection_handle] { gap.update_connParas(connection_handle); });
  } else {
    printf("Failed to connect\r\n");
  }
}

/** This is called by Gap to notify the application we disconnected */
void GapDemo::onDisconnectionComplete(
    const ble::DisconnectionCompleteEvent &event) {
  printf("Disconnected\r\n");
  ble::connection_handle_t connection_handle = event.getConnectionHandle();

  BleLinks::Link *link = blelinks.get_link_by_handle(connection_handle);
  if (link != nullptr) {
    blelinks.del_link(link);
  }
}
